import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict

class PERTAnalysis:
    """
    PERT三点估算与关键路径争议分析系统
    功能：
    1. 任务时间估算（PERT公式）
    2. 关键路径分析
    3. 敏感性分析和风险评估
    4. 争议点模拟与可视化
    """
    
    def __init__(self):
        # 初始化任务数据（基于文档中的路径②：1→2→4→6→7）
        self.tasks = {
            'Task1': {'o': 3, 'm': 5, 'p': 7, 'predecessors': []},
            'Task2': {'o': 2, 'm': 4, 'p': 6, 'predecessors': ['Task1']},
            'Task4': {'o': 6, 'm': 9, 'p': 12, 'predecessors': ['Task2'], 'controversy': True},
            'Task6': {'o': 3, 'm': 4, 'p': 5, 'predecessors': ['Task4'], 'controversy': True},
            'Task7': {'o': 2, 'm': 3, 'p': 4, 'predecessors': ['Task6']}
        }
        
        # 非关键路径（路径③）
        self.alt_tasks = {
            'Task5': {'o': 4, 'm': 6, 'p': 10, 'predecessors': ['Task2']}
        }
        
        # 争议点记录（文档中提到的三个争议）
        self.controversies = [
            {
                'id': 1,
                'task': 'Task4',
                'issue': '悲观时间(P=12)过高',
                'proposal': '调整为P=10',
                'impact': '总工期减少0.33天'
            },
            {
                'id': 2,
                'task': 'Task6',
                'issue': '乐观时间(O=3)被低估',
                'proposal': '调整为O=4',
                'impact': '总工期增加0.17天'
            },
            {
                'id': 3,
                'issue': '路径③缓冲不足',
                'proposal': '增加缓冲时间或重估Task5',
                'impact': '关键路径可能切换'
            }
        ]
        
        # 计算初始时间估算
        self.calculate_all_te()
        
    def calculate_te(self, o, m, p):
        """PERT公式计算预期时间"""
        return (o + 4*m + p) / 6
    
    def calculate_sd(self, o, p):
        """计算标准差"""
        return (p - o) / 6
    
    def calculate_all_te(self):
        """计算所有任务的TE和标准差"""
        for task in self.tasks:
            t = self.tasks[task]
            t['te'] = self.calculate_te(t['o'], t['m'], t['p'])
            t['sd'] = self.calculate_sd(t['o'], t['p'])
        
        for task in self.alt_tasks:
            t = self.alt_tasks[task]
            t['te'] = self.calculate_te(t['o'], t['m'], t['p'])
            t['sd'] = self.calculate_sd(t['o'], t['p'])
    
    def get_critical_path(self):
        """计算关键路径（简化版，假设只有两条路径）"""
        path2_duration = sum(self.tasks[t]['te'] for t in self.tasks)
        path3_duration = (self.tasks['Task1']['te'] + 
                         self.tasks['Task2']['te'] + 
                         self.alt_tasks['Task5']['te'])
        
        if path2_duration >= path3_duration:
            return {
                'path': ['Task1', 'Task2', 'Task4', 'Task6', 'Task7'],
                'duration': path2_duration,
                'type': 'original'
            }
        else:
            return {
                'path': ['Task1', 'Task2', 'Task5'],
                'duration': path3_duration,
                'type': 'alternative'
            }
    
    def evaluate_controversy(self, controversy_id):
        """
        评估特定争议点的影响
        参数:
        - controversy_id: 1/2/3 对应文档中的三个争议
        """
        if controversy_id == 1:
            # 争议1：调整Task4的悲观时间
            original_p = self.tasks['Task4']['p']
            adjusted_p = 10  # 调整为10天
            
            # 保存原始值
            self.tasks['Task4']['p_original'] = original_p
            self.tasks['Task4']['te_original'] = self.tasks['Task4']['te']
            
            # 应用调整
            self.tasks['Task4']['p'] = adjusted_p
            self.calculate_all_te()
            
            # 计算影响
            original_duration = sum(self.tasks[t]['te_original'] if t == 'Task4' else self.tasks[t]['te'] 
                                   for t in ['Task1', 'Task2', 'Task4', 'Task6', 'Task7'])
            new_duration = sum(self.tasks[t]['te'] for t in ['Task1', 'Task2', 'Task4', 'Task6', 'Task7'])
            
            return {
                'adjustment': f"Task4 P从{original_p}调整为{adjusted_p}",
                'original_te': self.tasks['Task4']['te_original'],
                'new_te': self.tasks['Task4']['te'],
                'original_path_duration': original_duration,
                'new_path_duration': new_duration,
                'impact': new_duration - original_duration
            }
            
        elif controversy_id == 2:
            # 争议2：调整Task6的乐观时间
            original_o = self.tasks['Task6']['o']
            adjusted_o = 4  # 调整为4天
            
            # 保存原始值
            self.tasks['Task6']['o_original'] = original_o
            self.tasks['Task6']['te_original'] = self.tasks['Task6']['te']
            
            # 应用调整
            self.tasks['Task6']['o'] = adjusted_o
            self.calculate_all_te()
            
            # 计算影响
            original_duration = sum(self.tasks[t]['te_original'] if t == 'Task6' else self.tasks[t]['te'] 
                                   for t in ['Task1', 'Task2', 'Task4', 'Task6', 'Task7'])
            new_duration = sum(self.tasks[t]['te'] for t in ['Task1', 'Task2', 'Task4', 'Task6', 'Task7'])
            
            return {
                'adjustment': f"Task6 O从{original_o}调整为{adjusted_o}",
                'original_te': self.tasks['Task6']['te_original'],
                'new_te': self.tasks['Task6']['te'],
                'original_path_duration': original_duration,
                'new_path_duration': new_duration,
                'impact': new_duration - original_duration
            }
            
        elif controversy_id == 3:
            # 争议3：评估路径③风险
            # 模拟Task5悲观时间增加
            original_p = self.alt_tasks['Task5']['p']
            adjusted_p = 14  # 增加到14天
            
            # 保存原始值
            self.alt_tasks['Task5']['p_original'] = original_p
            self.alt_tasks['Task5']['te_original'] = self.alt_tasks['Task5']['te']
            
            # 应用调整
            self.alt_tasks['Task5']['p'] = adjusted_p
            self.calculate_all_te()
            
            # 计算两条路径持续时间
            path2_duration = sum(self.tasks[t]['te'] for t in ['Task1', 'Task2', 'Task4', 'Task6', 'Task7'])
            path3_duration = (self.tasks['Task1']['te'] + 
                             self.tasks['Task2']['te'] + 
                             self.alt_tasks['Task5']['te'])
            
            # 检查关键路径是否变化
            original_critical = path2_duration >= (self.tasks['Task1']['te_original'] + 
                                                 self.tasks['Task2']['te_original'] + 
                                                 self.alt_tasks['Task5']['te_original'])
            new_critical = path2_duration >= path3_duration
            
            return {
                'adjustment': f"Task5 P从{original_p}调整为{adjusted_p}",
                'original_path3_duration': (self.tasks['Task1']['te_original'] + 
                                          self.tasks['Task2']['te_original'] + 
                                          self.alt_tasks['Task5']['te_original']),
                'new_path3_duration': path3_duration,
                'path2_duration': path2_duration,
                'critical_path_changed': original_critical != new_critical,
                'new_critical_path': 'path③' if path3_duration > path2_duration else 'path②'
            }
    
    def monte_carlo_simulation(self, n_simulations=10000):
        """
        蒙特卡洛模拟评估项目工期不确定性
        基于文档中σ值进行概率分析
        """
        # 收集所有任务数据
        all_tasks = {**self.tasks, ​**self.alt_tasks}
        
        # 准备模拟数据
        simulations = []
        for _ in range(n_simulations):
            # 为每个任务生成随机持续时间（正态分布）
            durations = {}
            for task in all_tasks:
                t = all_tasks[task]
                duration = np.random.normal(t['te'], t['sd'])
                durations[task] = max(duration, t['o'])  # 不低于乐观时间
            
            # 计算两条路径持续时间
            path2 = (durations['Task1'] + durations['Task2'] + 
                    durations['Task4'] + durations['Task6'] + durations['Task7'])
            path3 = durations['Task1'] + durations['Task2'] + durations['Task5']
            
            # 项目总工期由关键路径决定
            project_duration = max(path2, path3)
            simulations.append(project_duration)
        
        return simulations
    
    def plot_simulation_results(self, simulations):
        """可视化蒙特卡洛模拟结果"""
        plt.figure(figsize=(10, 6))
        
        # 直方图
        plt.hist(simulations, bins=50, alpha=0.7, color='skyblue', edgecolor='black')
        
        # 添加统计信息
        mean_duration = np.mean(simulations)
        median_duration = np.median(simulations)
        std_duration = np.std(simulations)
        
        plt.axvline(mean_duration, color='red', linestyle='dashed', linewidth=1)
        plt.axvline(median_duration, color='green', linestyle='dashed', linewidth=1)
        
        plt.title('项目工期概率分布（蒙特卡洛模拟）')
        plt.xlabel('项目总工期（天）')
        plt.ylabel('出现频率')
        
        stats_text = (f"均值: {mean_duration:.2f}天\n"
                     f"中位数: {median_duration:.2f}天\n"
                     f"标准差: {std_duration:.2f}天")
        plt.text(0.7, 0.9, stats_text, transform=plt.gca().transAxes, 
                bbox=dict(facecolor='white', alpha=0.5))
        
        plt.grid(True, alpha=0.3)
        plt.tight_layout()
        return plt
    
    def generate_controversy_report(self):
        """生成争议分析报告（Markdown格式）"""
        report = "# 时间估算争议分析报告\n\n"
        report += "## 争议背景\n"
        report += ("根据PERT三点估算法，项目关键路径为路径②(1→2→4→6→7)，"
                  "总预期工期为27.5天。但团队成员对以下问题存在分歧：\n\n")
        
        # 添加三个争议点分析
        for controversy in self.controversies:
            report += f"## 争议点{controversy['id']}: {controversy['issue']}\n"
            report += f"- ​**涉及任务**: {controversy.get('task', 'N/A')}\n"
            report += f"- ​**调整建议**: {controversy['proposal']}\n"
            report += f"- ​**预期影响**: {controversy['impact']}\n\n"
            
            # 添加详细评估
            analysis = self.evaluate_controversy(controversy['id'])
            report += "### 详细影响评估\n"
            
            if controversy['id'] in [1, 2]:
                report += (f"- 任务TE从 {analysis['original_te']:.2f}天 → {analysis['new_te']:.2f}天\n"
                         f"- 路径②工期从 {analysis['original_path_duration']:.2f}天 → {analysis['new_path_duration']:.2f}天\n"
                         f"- 净影响: {analysis['impact']:.2f}天\n\n")
            else:
                report += (f"- 路径③工期从 {analysis['original_path3_duration']:.2f}天 → {analysis['new_path3_duration']:.2f}天\n"
                         f"- 当前路径②工期: {analysis['path2_duration']:.2f}天\n"
                         f"- 关键路径变化: {'是' if analysis['critical_path_changed'] else '否'}\n"
                         f"- 新关键路径: {analysis['new_critical_path']}\n\n")
        
        # 添加建议（文档最后部分）
        report += "## 争议解决建议\n"
        report += ("1. 召开风险评估会议，重新评审任务4、任务6的估算依据\n"
                  "2. 若关键路径调整，需同步更新项目缓冲期\n"
                  "3. 对路径③进行敏感性分析，明确其潜在风险对总工期的影响\n\n")
        report += f"​**解决期限**: 需在3个工作日内达成共识\n"
        
        return report


# 示例使用
if __name__ == "__main__":
    analyzer = PERTAnalysis()
    
    # 打印初始关键路径
    critical_path = analyzer.get_critical_path()
    print(f"初始关键路径: {critical_path['path']}")
    print(f"初始预期工期: {critical_path['duration']:.2f}天\n")
    
    # 评估争议点1：Task4悲观时间
    print("评估争议点1：Task4悲观时间过高")
    analysis1 = analyzer.evaluate_controversy(1)
    print(f"调整: {analysis1['adjustment']}")
    print(f"Task4 TE变化: {analysis1['original_te']:.2f} → {analysis1['new_te']:.2f}")
    print(f"路径②工期变化: {analysis1['original_path_duration']:.2f} → {analysis1['new_path_duration']:.2f}")
    print(f"净影响: {analysis1['impact']:.2f}天\n")
    
    # 评估争议点3：路径③风险
    print("评估争议点3：路径③缓冲不足风险")
    analysis3 = analyzer.evaluate_controversy(3)
    print(f"调整: {analysis3['adjustment']}")
    print(f"路径③工期变化: {analysis3['original_path3_duration']:.2f} → {analysis3['new_path3_duration']:.2f}")
    print(f"关键路径是否变化: {'是' if analysis3['critical_path_changed'] else '否'}")
    print(f"新关键路径: {analysis3['new_critical_path']}\n")
    
    # 蒙特卡洛模拟
    print("运行蒙特卡洛模拟...")
    simulations = analyzer.monte_carlo_simulation(n_simulations=10000)
    plot = analyzer.plot_simulation_results(simulations)
    plot.savefig('pert_simulation.png')
    print("已保存模拟结果图表: pert_simulation.png\n")
    
    # 生成争议报告
    report = analyzer.generate_controversy_report()
    with open('pert_controversy_report.md', 'w', encoding='utf-8') as f:
        f.write(report)
    print("已生成争议分析报告: pert_controversy_report.md")